#!/usr/bin/env python2
# ----------------------------------------------------------------------------
# Eventually this declaration will be in every source file...
#
# Copyright 2021 Chris Baird <vk2cjb@gmail.com>
# This file is a part of QRPBBB.
# QRPBBB is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
# For a copy of the GNU General Public License, see <https://www.gnu.org/licenses/>.

import os, zlib, re
import site_config as cfg

#
#
#

def message_hash(message):
   # Ugh. There's mutable header lines (Path:, Date:), which have to be
   # excluded from the checksum

   munged = ""
   for l in message.splitlines():
      # todo: check this only in the header
      if re.match ("^X-QRPBBB: ", l):
         continue
      if re.match ("^Path: ", l):
         continue
      if re.match ("^Date: ", l):
         continue
      munged += l + "\n"

   return "%08x" % (zlib.adler32(munged) & 0xffffffff)

#
#
#

def fetch_messagetext (filename):
   foo = open(filename, mode='rb')
   all = foo.read()
   foo.close()

   msg = ""
   for l in all.splitlines():
      if cfg.NEWSSPOOL in filename and re.match ('^X-QRPBBB: ', l):
         continue
      msg += l + "\n"

   return msg

#
# Retrieves file and indicates missing lines
#

def fetch_message (filename):
   body = fetch_messagetext(filename).splitlines()
   message = {}
   i = 1
   for l in body:
      # does this
      message[i] = l.rstrip("\n")
      if cfg.QUEUE_IN in filename:
         if cfg.MISSINGLINE in l:
            message[i] = int(-1)
      i = i + 1

   return message

#
#
#

def newsspool_filelist():
   ret = []

   for root, dirs, files in os.walk(cfg.NEWSSPOOL):
      if "junk" in root:
         continue
      if "." in root:
         continue
      for name in files:
         if "." in name:
            continue
         f = os.path.join (root, name)
         # Check for the presence of QRPBBB header. Ignore if missing.
         foo = open (f, mode='rb')
         m = foo.readlines()
         foo.close()
         q = filter(lambda v: re.match('^X-QRPBBB:', v), m)
         if len(q) > 0:
            q = q[0].rstrip()
            ret.append(f)
         else:
            print "Skipping unscrubbed file",f
   return ret

#
#
#

def queuein_filelist():
   ret = []

   for root, dirs, files in os.walk(cfg.QUEUE_IN):
      for name in files:
         f = os.path.join (root, name)
         ret.append(f)

   return ret

#
# 1,3-6,8 -> 1,3,4,5,6,8
#

def expand_linespec (spec):
   enabled = {}

   while len(spec) > 0:
      commasplit = spec.split(',',1)
      rangesplit = spec.split('-',1)

      if len(commasplit[0]) == len(rangesplit[0]):
         v = int(commasplit[0])
         enabled[v] = 1
         spec = ""
      else:
         if len(commasplit[0]) < len(rangesplit[0]):
            v = int(commasplit[0])
            enabled[v] = 1
            spec = str(commasplit[1]).strip('[]')
         else:
            l = int(rangesplit[0])
            rightsplit = str(rangesplit[1]).strip('[]')
            rhs = rightsplit.split(',',1)
            r = int(rhs[0])
            for v in range(l,r+1):
               enabled[v] = 1
            if len(rhs) > 1:
               spec = str(rhs[1].strip('[]'))
            else:
               spec = ""

   ret = []
   for k,v in sorted(enabled.items()):
      ret.append(k)

   return (ret, enabled)

#
# 1,3,4,5,6,8 -> 1,3-6,8
#

def create_linespec (nums):
   ret = ""
   s = 0

   while s < len(nums):
      i = s
      while i < len(nums)-1 and nums[i]+1 == nums[i+1]:
         i += 1
      ret += "," + str(nums[s])
      if i != s:
         ret += "-" + str(nums[i])
      s = i + 1

   return ret.lstrip(',')

#
#
#

def linespec_split (listnums):
   ret = []
   a = []

   while len(listnums) > 0:
      ls = create_linespec(a)
      if len(ls) < 128:
         a.append(listnums[0])
         listnums = listnums[1:]
         continue
      ret.append(ls)
      a = []
   ret.append(create_linespec(a))

   return ret

#
#
#

# for catching near-immediate packet dups, dunno if it works properly yet
packetlog = []

def packetout (tncfmt, priority):
   global packetlog

   # lazy check for object existance..
   try:
      packetlog.index (tncfmt)
   except:
      contenthash = "%08x" % (zlib.adler32(tncfmt) & 0xffffffff)
      fn = os.path.join (cfg.QUEUE_OUT, "%06d" % priority + "-" + contenthash)
      print fn, tncfmt
      packetlog.append(tncfmt)
      foo = open (fn, mode="w")
      foo.write(tncfmt+"\n")
      foo.close()

#
#
#

def update_message (msgid, message):
   # handle 'sparse' lines, replacing missing ones with cfg.MISSINGLINE

   filename = os.path.join (cfg.QUEUE_IN, msgid)
   foo = open (filename, mode='w')
   print "Writing", filename

   li = 1
   for k,v in sorted (message.items()):
      while li != k:
         foo.write (cfg.MISSINGLINE + "\n")
         li = li + 1

      s = v
      if isinstance (v, int):
         s = cfg.MISSINGLINE
      foo.write (s + "\n")
      li = li + 1

   foo.close()

#
# See if the msgid is being ignored
#

badmsgs = []

def badmsg_check (msgid):
   global badmsgs

   if not len(badmsgs):
      # read in
      try:
         foo = open (cfg.BADLIST, mode='r')
         for l in foo.readlines():
            badmsgs.append(l.rstrip())
         foo.close()
      except:
         badmsgs = {}

   try:
      badmsgs.index(msgid)
      return True
   except:
      return False

#
#
#

def badmsg_append (msgid):
   if not badmsg_check(msgid):
      foo = open (cfg.BADLIST, mode='a')
      foo.write (msgid + "\n")
      foo.close()
      badmsgs.append(msgid)

# A 21st Century algorithm of just scanning the everything instead of
# maintaining a persistent database. At least it's only done once here.
# process does get executed relatively freuently, but external databases are
# bad for system integrity.
# todo: make msgsdb a global?

def msgids_scan():
   ret = {}
   fl = newsspool_filelist()
   for f in fl:
      body = fetch_messagetext (f)
      msgid = message_hash (body)
      ret[msgid] = f
   fl = queuein_filelist()
   for f in fl:
      name = os.path.basename (f)
      ret[name] = f
   return ret

#
# Generates S:end packets for a msgid and linenumber list
#

def qrpbbb_request (msgidsdb, msgid, linespec):
   # not working properly?
   f = msgidsdb.get(msgid)
   if f == None:
      return
   if badmsg_check(msgid):
      return

   (wantedlines, j) = expand_linespec(linespec)
   message = fetch_message (f)

   seen = {}
   for li in wantedlines:
      try:
         if isinstance(message[li], str):
            # PROBLEM - ~O(n^2) !! .. relatively slow on the WIA broadcast
            # fixed issue: the linespec for all the blank lines in large
            # messages can be far too long-- so have to handle in parts.
            duplines = []
            for i in range (li, len(message)+1) + range (1,li):
               if seen.get(i) == None and message[i] == message[li]:
                  duplines.append(i)
                  if len(create_linespec(duplines)) > 128:
                     break;

            # prevent the above from listing the same linenumbers
            for l in duplines:
               seen[l] = 1

            if len(duplines) > 0:
               tncline = cfg.APRSPREAM + "S:" + msgid + ","
               tncline += create_linespec(duplines) + ":" + message[li]
               packetout (tncline, priority=200+li)
      except:
         # makes the handling of missing and blank lines simpler
         continue

#
# ----------------------------------------------------------------------------
#
